﻿using System;
using System.Collections.Generic;
using System.Text;

namespace SqlServer.Profiler
{
	public class ProfileFilter
	{
		public enum Op {eq, noteq, like, notlike, gt, lt, gteq, lteq}
		static String[] operatorCodes = {"==","!=","%%", "!%", ">>", "<<", ">=", "<="};
		static String[] operatorNames = {"=","<>","Like", "Not Like", ">", "<", ">=", "<="};

		public Op Operator {get; set; }
		public String OperatorCode {get { return GetOpCode(this.Operator, true); } }
		public String OperatorName {get { return GetOpCode(this.Operator, false); } }

		public int ColumnId {get; set; }
		public String Rhs {get; set; }

		public ProfileFilter() {}

		public ProfileFilter(int columnId, Op operatorIn, String rhs)
		{
			this.ColumnId = columnId;
			this.Operator = operatorIn;
			this.Rhs = rhs;
		}

		public override string ToString()
		{
			return String.Format("{0}{1}{2}", this.ColumnId, this.OperatorCode, this.Rhs);
		}
		
		// parse a memo string into a ProfileFilter object
		public static bool TryParse(String filterString, out ProfileFilter filter)
		{
			filter = new ProfileFilter();
			// empty string fails parse
			if (String.IsNullOrEmpty(filterString))
				return false;
			int operatorPos = FindOperator(filterString);
			// string must contain a recognizable op-code eg "==", ">=" etc
			if (operatorPos == -1) return false; // failed parse
			String part = filterString.Substring(0, operatorPos);
			// left of the operator must be a column Id value
			int colId;
			if (!int.TryParse(part, out colId))
				return false;
			filter.ColumnId = colId;
			part = filterString.Substring(operatorPos, 2);
			// hm. below should never return false if the test above is good!
			Op operatorOut;
			if (!TryParse(part, out operatorOut))
				return false;
			filter.Operator = operatorOut;
			// the rest is the string beyond the operator code
			filter.Rhs =  filterString.Substring(operatorPos + 2);
			return true;
		}
		
		// Look for the 2-character opcode in the memo string
		private static int FindOperator(string filterString)
		{
			String seekString = filterString; 
			// modify seekString to remove quoted text if being absolutely correct.
			foreach (String op in operatorCodes)
			{
				int pos = seekString.IndexOf(op);
				if (pos != -1)
					return pos; 
			}
			return -1;
		}

		// resolve a two code op-code to an enum
		public static bool TryParse(String operatorString, out Op operatorOut)
		{
			operatorOut = Op.eq;
			bool parsedOk = false;
			for (int idx = 0; idx < operatorCodes.GetLength(0); ++idx)
			{
				if (operatorString == operatorCodes[idx])
				{
					operatorOut = (Op)idx;
					return true;
				}
			}
			for (int idx = 0; idx < operatorNames.GetLength(0); ++idx)
			{
				if (operatorString == operatorNames[idx])
				{
					operatorOut = (Op)idx;
					return true;
				}
			}
			return parsedOk;
		}
		
		// convert the operator enum to either a code or name
		// shortCode = T for code F for name;
		public static string GetOpCode(Op operatorParam, bool shortCode)
		{
			int idx = Convert.ToInt32(operatorParam);
			if (shortCode)
				return operatorCodes[idx];
			else
				return operatorNames[idx];
		}
		
		//
		// Handle multiple filter terms for the same column:
		// Spec:
		// separate terms with space , & (AND) or | (OR)
		// Check the Operator: 
		//		if it is Like then set connectwithOr=T unless "&" is the separator
		//		if it is Not like then set connectwithOr=F
		//		if it is = then set connectWithOr=F
		//		if it is != then set connectWithOr=T
		// Trim and return the broken up strings
		public static String[] ParseStringFilter(String filterIn, Op operatorParam, out bool connectWithOr)
		{
			String filter = filterIn.Trim();
			char delim = (filter.IndexOf('|') != -1) ? '|' :
						 (filter.IndexOf('&') != -1) ? '&' : 
						 (filter.IndexOf(',') != -1) ? ',' : ' ';
			switch (operatorParam)
			{
				case Op.like:
					connectWithOr = (delim == '&') ? false : true;
					break;
				case Op.noteq:
				case Op.notlike:
					connectWithOr = false;
					break;
				case Op.eq:
					connectWithOr = true;
					break;
				default: // no Idea. Just be cautious
					connectWithOr = true;
					break;
			}
			// deal with (ie remove) variable spacing around the filter values.
			String delimString = delim.ToString();
			String seek = delimString+" ";
			while (filter.IndexOf(seek) > -1)
				filter = filter.Replace(seek, delimString);
			seek = " "+delimString;
			while (filter.IndexOf(seek) > -1)
				filter = filter.Replace(seek, delimString);
			String[] parts = filter.Split(delim);
			return parts;
		}
	}

	public class ProfileFilterTests
	{
		public static void DoTests()
		{
			ProfileFilter f;
			if (!ProfileFilter.TryParse("2==THis", out f))
				throw new Exception("Test 1 fail");
			if (!(f.ColumnId == 2))
				throw new Exception("Test 2 fail");
			if (!(f.Operator == ProfileFilter.Op.eq))
				throw new Exception("Test 3 fail");
			if (!(f.Rhs == "THis"))
				throw new Exception("Test 4 fail");
			if (!(f.OperatorCode == "=="))
				throw new Exception("Test 5 fail");
			if (!(f.OperatorName == "="))
				throw new Exception("Test 6 fail");
			bool useOr;
			String [] parts = ProfileFilter.ParseStringFilter("A&B", ProfileFilter.Op.like, out useOr);
			if (parts.GetLength(0) != 2) 
				throw new Exception("Test 7 fail");
			if (parts[0] != "A" || parts[1] != "B") 
				throw new Exception("Test 8 fail");
			if (useOr == true)
				throw new Exception("Test 9 fail");
			parts = ProfileFilter.ParseStringFilter("A B", ProfileFilter.Op.notlike, out useOr);
			if (parts.GetLength(0) != 2) 
				throw new Exception("Test 10 fail");
			if (parts[0] != "A" || parts[1] != "B") 
				throw new Exception("Test 11 fail");
			if (useOr == true)
				throw new Exception("Test 12 fail");
			parts = ProfileFilter.ParseStringFilter("A,B", ProfileFilter.Op.like, out useOr);
			if (parts.GetLength(0) != 2) 
				throw new Exception("Test 13 fail");
			if (parts[0] != "A" || parts[1] != "B") 
				throw new Exception("Test 14 fail");
			if (useOr == false)
				throw new Exception("Test 15 fail");
			parts = ProfileFilter.ParseStringFilter("  A   |   B  ", ProfileFilter.Op.eq, out useOr);
			if (parts.GetLength(0) != 2) 
				throw new Exception("Test 16 fail");
			if (parts[0] != "A" || parts[1] != "B") 
				throw new Exception("Test 17 fail");
			if (useOr == false)
				throw new Exception("Test 18 fail");
			parts = ProfileFilter.ParseStringFilter("  A      B  ", ProfileFilter.Op.noteq, out useOr);
			if (parts.GetLength(0) != 2) 
				throw new Exception("Test 16 fail");
			if (parts[0] != "A" || parts[1] != "B") 
				throw new Exception("Test 17 fail");
			if (useOr == true)
				throw new Exception("Test 18 fail");
		}
	}
}
